from typing import Any, List, Optional, Dict, TypedDict, Union

from django.template.loader import render_to_string
from django.shortcuts import render
from django.core.handlers.wsgi import WSGIRequest
from django.http import JsonResponse, HttpResponseNotFound, HttpResponse
from django.utils.safestring import SafeString

from . import models


class BasicInformationOfMenuElement(TypedDict):
    """ Основная информация элемента меню """

    menu_element_level: int
    menu_element_id: int


class MenuUtils:
    """ Инструменты для работы с меню """

    def get_first_level_of_menu_elements(
            self,
            menu_name: str,
            fields: Optional[List[str]] = None
            ) -> List[Dict[str, str]]:
        """ Получаем первый уровень элементов меню """

        initial_fields = ['id', 'name']
        fields = fields if fields else initial_fields
        first_level_menu_elements = list(models.FirstLevelElement.objects.filter(
            menu__name=menu_name
        ).values(*fields))
        return first_level_menu_elements


    def get_context_for_single_menu(
            self,
            menu_name: str,
            list_of_menu_element_fields: Optional[List[str]] = None
            ) -> Dict[str, Any]:
        """ Получаем контекст для отдельного меню """

        first_level_menu_elements = self.get_first_level_of_menu_elements(
            menu_name,
            list_of_menu_element_fields
        )
        context = {
            'menu_element_level': 1,
            'menu_name': menu_name,
            'first_level_menu_elements': first_level_menu_elements
        }
        return context


    def get_initial_menu_format_as_html(self, menu_name: str) -> SafeString:
        """ Получаем начальный формат меню в виде html """

        context = self.get_context_for_single_menu(menu_name)
        return render_to_string('menu/menu_home_page.html', context)


    def get_list_of_menu_names_as_html(self) -> SafeString:
        """ Получаем список из названий меню в виде html """

        list_of_menu_names = models.Menu.objects.all().values('name')
        context = {'list_of_menu_names': list_of_menu_names}
        return render_to_string('menu/list_of_menu_names.html', context)


    def get_menu_element_by_parameters(
            self,
            element: BasicInformationOfMenuElement) -> models:
        """ Получаем элемент меню по его параметрам """

        list_of_models = {
            1: models.FirstLevelElement,
            2: models.SecondLevelElement,
            3: models.ThirdLevelElement,
        }
        model = list_of_models.get(element['menu_element_level'])
        menu_element = model.objects.get(pk=element['menu_element_id'])
        return menu_element


    def get_subelements_of_element(
            self,
            element: BasicInformationOfMenuElement,
            fields: Optional[List[str]] = None
            ) -> Dict[int, Dict]:
        """ Получаем подэлементы элемента меню """

        initial_fields = ['id', 'name']
        fields = fields if fields else initial_fields
        menu_element = self.get_menu_element_by_parameters(element)
        menu_element_level = element['menu_element_level']

        if menu_element_level == 1:
            subelements = models.SecondLevelElement.objects.filter(
                first_level__pk=menu_element.pk
            )
        elif menu_element_level == 2:
            subelements = models.ThirdLevelElement.objects.filter(
                second_level__pk=menu_element.pk
            )
        else:
            message = 'Такого уровня элементов нет'
            raise ValueError(message)

        subelements = enumerate(list(
            subelements.values(*fields)
        ))

        return {count: subelement for count, subelement in subelements}


    def get_subelements_to_display(self, request: WSGIRequest) -> JsonResponse:
        """ Получаем подэлементы для отображения """

        basic_information_of_menu_element = BasicInformationOfMenuElement(
            menu_element_level=int(request.GET['menu_element_level']),
            menu_element_id=int(request.GET['menu_element_id'])
        )

        list_of_menu_element_fields = ['id', 'name', 'description'] if 'single_menu' in request.GET else None
        subelements_of_element = self.get_subelements_of_element(
            basic_information_of_menu_element,
            fields=list_of_menu_element_fields
        )

        return JsonResponse(subelements_of_element)


    def show_single_menu(self, request: WSGIRequest, menu_name: str) -> HttpResponse:
        """ Показываем отдельное меню """

        list_of_menu_element_fields = ['id', 'name', 'description']
        context = self.get_context_for_single_menu(
            menu_name,
            list_of_menu_element_fields
        )
        return render(request, 'menu/single_menu.html', context)


    def get_necessary_information(
            self,
            request: WSGIRequest
            ) -> Union[HttpResponse, JsonResponse, HttpResponseNotFound]:
        """ Получаем необходимую информацию """

        if 'get_subelements_for_menu' in request.GET:
            return self.get_subelements_to_display(request)
        else:
            message = f'Такой страницы не существует'
            return HttpResponseNotFound(message)
